﻿using System;
using System.Collections.Generic;
using l1cs.server.model.Instance;
using l1cs.server.model.map;
using l1cs.server.model.poison;
using l1cs.server.model.skill;
using l1cs.server.server.types;
using l1cs.server.serverpackets;
using l1cs.server.utils;
using System.Threading;
using System.Collections;
using System.Diagnostics;

namespace l1cs.server.model
{
    public class L1Character : L1Object
    {
        private static Logger _log = Logger.getLogger( typeof( L1Character ).FullName );

        private L1Poison _poison = null;
        private bool _paralyzed;
        private bool _sleeped;

        private Dictionary<int, L1NpcInstance> _petlist = new Dictionary<int, L1NpcInstance>();
        private Dictionary<int, L1DollInstance> _dolllist = new Dictionary<int, L1DollInstance>();
        private Dictionary<int, L1SkillTimer> _skillEffect = new Dictionary<int, L1SkillTimer>();
        private Dictionary<int, L1ItemDelay> _itemdelay = new Dictionary<int, L1ItemDelay>();
        private Dictionary<int, L1FollowerInstance> _followerlist = new Dictionary<int, L1FollowerInstance>();

        public L1Character()
        {
            _level = 1;
        }

        /// <summary>
        /// キャラクターを復活させる。
        /// </summary>
        /// <param name="hp">復活後のHP</param>
        virtual public void resurrect( int hp )
        {
            if ( !isDead() )
            {
                return;
            }
            if ( hp <= 0 )
            {
                hp = 1;
            }
            setCurrentHp( hp );
            setDead( false );
            setStatus( 0 );
            L1PolyMorph.undoPoly( this );
            foreach ( L1PcInstance pc in L1World.getInstance().getRecognizePlayer( this ) )
            {
                pc.sendPackets( new S_RemoveObject( this ) );
                pc.removeKnownObject( this );
                pc.updateObject();
            }
        }

        private int _currentHp;

        /// <summary>
        /// キャラクターの現在のHPを返す。
        /// </summary>
        /// <returns>現在のHP</returns>
        public int getCurrentHp()
        {
            return _currentHp;
        }

        /// <summary>
        /// キャラクターのHPを設定する。
        /// </summary>
        /// <param name="i">キャラクターの新しいHP</param>
        /// <remarks>特殊な処理がある場合はこっちをオーバライド（パケット送信等）</remarks>
        virtual public void setCurrentHp( int i )
        {
            _currentHp = i;
            if ( _currentHp >= getMaxHp() )
            {
                _currentHp = getMaxHp();
            }
        }

        /// <summary>
        /// キャラクターのHPを設定する。
        /// </summary>
        /// <param name="i">キャラクターの新しいHP</param>
        public void setCurrentHpDirect( int i )
        {
            _currentHp = i;
        }

        private int _currentMp;

        /// <summary>
        /// キャラクターの現在のMPを返す。
        /// </summary>
        /// <returns>現在のMP</returns>
        public int getCurrentMp()
        {
            return _currentMp;
        }

        /// <summary>
        /// キャラクターのMPを設定する。
        /// 特殊な処理がある場合はこっちをオーバライド（パケット送信等）
        /// </summary>
        /// <param name="i">キャラクターの新しいMP</param>
        virtual
        public void setCurrentMp( int i )
        {
            _currentMp = i;
            if ( _currentMp >= getMaxMp() )
            {
                _currentMp = getMaxMp();
            }
        }

        /// <summary>
        /// キャラクターのMPを設定する。
        /// </summary>
        /// <param name="i">キャラクターの新しいMP</param>
        public void setCurrentMpDirect( int i )
        {
            _currentMp = i;
        }

        /// <summary>
        /// キャラクターの眠り状態を返す。
        /// </summary>
        /// <returns>眠り状態を表す値。眠り状態であればtrue。</returns>
        public bool isSleeped()
        {
            return _sleeped;
        }

        /// <summary>
        /// キャラクターの眠り状態を設定する。
        /// </summary>
        /// <param name="sleeped">眠り状態を表す値。眠り状態であればtrue。</param>
        public void setSleeped( bool sleeped )
        {
            _sleeped = sleeped;
        }

        /// <summary>
        /// キャラクターの麻痺状態を返す。
        /// </summary>
        /// <returns>麻痺状態を表す値。麻痺状態であればtrue。</returns>
        public bool isParalyzed()
        {
            return _paralyzed;
        }

        /// <summary>
        /// キャラクターの麻痺状態を設定する。
        /// </summary>
        /// <param name="paralyzed">麻痺状態を表す値。麻痺状態であればtrue。</param>
        public void setParalyzed( bool paralyzed )
        {
            _paralyzed = paralyzed;
        }

        L1Paralysis _paralysis;

        public L1Paralysis getParalysis()
        {
            return _paralysis;
        }

        public void setParalaysis( L1Paralysis p )
        {
            _paralysis = p;
        }

        public void cureParalaysis()
        {
            if ( _paralysis != null )
            {
                _paralysis.cure();
            }
        }

        /// <summary>
        /// キャラクターの可視範囲に居るプレイヤーへ、パケットを送信する。
        /// </summary>
        /// <param name="packet">送信するパケットを表すServerBasePacketオブジェクト。</param>
        public void broadcastPacket( ServerBasePacket packet )
        {
            foreach ( L1PcInstance pc in L1World.getInstance().getVisiblePlayer( this ) )
            {
                _log.info( "broadcastPacket loop" );
                pc.sendPackets( packet );
            }
        }

        /// <summary>
        /// キャラクターの可視範囲に居るプレイヤーへ、パケットを送信する。 ただしターゲットの画面内には送信しない。
        /// </summary>
        /// <param name="packet">送信するパケットを表すServerBasePacketオブジェクト。</param>
        /// <param name="target"></param>
        public void broadcastPacketExceptTargetSight( ServerBasePacket packet, L1Character target )
        {
            foreach ( L1PcInstance pc in L1World.getInstance().getVisiblePlayerExceptTargetSight( this, target ) )
            {
                _log.info( "broadcastPacketExceptTargetSight loop" );
                pc.sendPackets( packet );
            }
        }

        /// <summary>
        /// キャラクターの可視範囲でインビジを見破れるor見破れないプレイヤーを区別して、パケットを送信する。
        /// </summary>
        /// <param name="packet">送信するパケットを表すServerBasePacketオブジェクト。</param>
        /// <param name="isFindInvis">true : 見破れるプレイヤーにだけパケットを送信する。 false : 見破れないプレイヤーにだけパケットを送信する。</param>
        public void broadcastPacketForFindInvis( ServerBasePacket packet, bool isFindInvis )
        {
            foreach ( L1PcInstance pc in L1World.getInstance().getVisiblePlayer( this ) )
            {
                if ( isFindInvis )
                {
                    if ( pc.hasSkillEffect( L1SkillId.GMSTATUS_FINDINVIS ) )
                    {
                        pc.sendPackets( packet );
                    }
                }
                else
                {
                    if ( !pc.hasSkillEffect( L1SkillId.GMSTATUS_FINDINVIS ) )
                    {
                        pc.sendPackets( packet );
                    }
                }
            }
        }

        /// <summary>
        /// キャラクターの50マス以内に居るプレイヤーへ、パケットを送信する。
        /// </summary>
        /// <param name="packet">送信するパケットを表すServerBasePacketオブジェクト。</param>
        public void wideBroadcastPacket( ServerBasePacket packet )
        {
            foreach ( L1PcInstance pc in L1World.getInstance().getVisiblePlayer( this, 50 ) )
            {
                pc.sendPackets( packet );
            }
        }

        /// <summary>
        /// キャラクターの正面の座標を返す。
        /// </summary>
        /// <returns>正面の座標</returns>
        public int[] getFrontLoc()
        {
            int[] loc = new int[2];
            int x = getX();
            int y = getY();
            int heading = getHeading();
            if ( heading == 0 )
            {
                y--;
            }
            else if ( heading == 1 )
            {
                x++;
                y--;
            }
            else if ( heading == 2 )
            {
                x++;
            }
            else if ( heading == 3 )
            {
                x++;
                y++;
            }
            else if ( heading == 4 )
            {
                y++;
            }
            else if ( heading == 5 )
            {
                x--;
                y++;
            }
            else if ( heading == 6 )
            {
                x--;
            }
            else if ( heading == 7 )
            {
                x--;
                y--;
            }
            loc[0] = x;
            loc[1] = y;
            return loc;
        }

        /// <summary>
        /// 指定された座標に対する方向を返す。
        /// </summary>
        /// <param name="tx">座標のX値</param>
        /// <param name="ty">座標のY値</param>
        /// <returns>指定された座標に対する方向</returns>
        public int targetDirection( int tx, int ty )
        {
            float dis_x = Math.Abs( getX() - tx ); // Ｘ方向のターゲットまでの距離
            float dis_y = Math.Abs( getY() - ty ); // Ｙ方向のターゲットまでの距離
            float dis = Math.Max( dis_x, dis_y ); // ターゲットまでの距離
            if ( dis == 0 )
            {
                return getHeading(); // 同じ位置ならいま向いてる方向を返しとく
            }
            int avg_x = (int)Math.Floor( ( dis_x / dis ) + 0.59f ); // 上下左右がちょっと優先な丸め
            int avg_y = (int)Math.Floor( ( dis_y / dis ) + 0.59f ); // 上下左右がちょっと優先な丸め

            int dir_x = 0;
            int dir_y = 0;
            if ( getX() < tx )
            {
                dir_x = 1;
            }
            if ( getX() > tx )
            {
                dir_x = -1;
            }
            if ( getY() < ty )
            {
                dir_y = 1;
            }
            if ( getY() > ty )
            {
                dir_y = -1;
            }

            if ( avg_x == 0 )
            {
                dir_x = 0;
            }
            if ( avg_y == 0 )
            {
                dir_y = 0;
            }

            if ( dir_x == 1 && dir_y == -1 )
            {
                return 1; // 上
            }
            if ( dir_x == 1 && dir_y == 0 )
            {
                return 2; // 右上
            }
            if ( dir_x == 1 && dir_y == 1 )
            {
                return 3; // 右
            }
            if ( dir_x == 0 && dir_y == 1 )
            {
                return 4; // 右下
            }
            if ( dir_x == -1 && dir_y == 1 )
            {
                return 5; // 下
            }
            if ( dir_x == -1 && dir_y == 0 )
            {
                return 6; // 左下
            }
            if ( dir_x == -1 && dir_y == -1 )
            {
                return 7; // 左
            }
            if ( dir_x == 0 && dir_y == -1 )
            {
                return 0; // 左上
            }
            return getHeading(); // ここにはこない。はず
        }

        /// <summary>
        /// 指定された座標までの直線上に、障害物が存在*しないか*を返す。
        /// </summary>
        /// <param name="tx">座標のX値</param>
        /// <param name="ty">座標のY値</param>
        /// <returns>障害物が無ければtrue、あればfalseを返す。</returns>
        public bool glanceCheck( int tx, int ty )
        {
            L1Map map = getMap();
            int chx = getX();
            int chy = getY();
            int arw = 0;
            for ( int i = 0; i < 15; i++ )
            {
                if ( ( chx == tx && chy == ty ) || ( chx + 1 == tx && chy - 1 == ty )
                        || ( chx + 1 == tx && chy == ty )
                        || ( chx + 1 == tx && chy + 1 == ty )
                        || ( chx == tx && chy + 1 == ty )
                        || ( chx - 1 == tx && chy + 1 == ty )
                        || ( chx - 1 == tx && chy == ty )
                        || ( chx - 1 == tx && chy - 1 == ty )
                        || ( chx == tx && chy - 1 == ty ) )
                {
                    break;

                }
                else if ( chx < tx && chy == ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 2)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chx++;
                }
                else if ( chx == tx && chy < ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 4)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chy++;
                }
                else if ( chx > tx && chy == ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 6)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chx--;
                }
                else if ( chx == tx && chy > ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 0)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chy--;
                }
                else if ( chx < tx && chy > ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 1)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chx++;
                    chy--;
                }
                else if ( chx < tx && chy < ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 3)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chx++;
                    chy++;
                }
                else if ( chx > tx && chy < ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 5)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chx--;
                    chy++;
                }
                else if ( chx > tx && chy > ty )
                {
                    // if (!map.isArrowPassable(chx, chy, 7)) {
                    if ( !map.isArrowPassable( chx, chy, targetDirection( tx, ty ) ) )
                    {
                        return false;
                    }
                    chx--;
                    chy--;
                }
            }
            if ( arw == 0 )
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 指定された座標へ攻撃可能であるかを返す。
        /// </summary>
        /// <param name="x">座標のX値。</param>
        /// <param name="y">座標のY値。</param>
        /// <param name="range">攻撃可能な範囲(タイル数)</param>
        /// <returns>攻撃可能であればtrue,不可能であればfalse</returns>
        public bool isAttackPosition( int x, int y, int range )
        {
            if ( range >= 7 ) // 遠隔武器（７以上の場合斜めを考慮すると画面外に出る)
            {
                if ( getLocation().getTileDistance( new Point( x, y ) ) > range )
                {
                    return false;
                }
            }
            else // 近接武器
            {
                if ( getLocation().getTileLineDistance( new Point( x, y ) ) > range )
                {
                    return false;
                }
            }
            return glanceCheck( x, y );
        }

        /// <summary>
        /// キャラクターのインベントリを返す。
        /// </summary>
        /// <returns>ャラクターのインベントリを表す、L1Inventoryオブジェクト。</returns>
        virtual public L1PcInventory getInventory()
        {
            return null;
        }

        /// <summary>
        /// キャラクターへ、新たにスキル効果を追加する。
        /// </summary>
        /// <param name="skillId">追加する効果のスキルID。</param>
        /// <param name="timeMillis">追加する効果の持続時間。無限の場合は0。</param>
        private void addSkillEffect( int skillId, int timeMillis )
        {
            L1SkillTimer timer = null;
            if ( 0 < timeMillis )
            {
                timer = L1SkillTimerCreator.create( this, skillId, timeMillis );
                timer.begin();
            }
            _skillEffect[skillId] = timer;
        }

        /// <summary>
        /// キャラクターへ、スキル効果を設定する。
        /// 重複するスキルがない場合は、新たにスキル効果を追加する。
        /// 重複するスキルがある場合は、残り効果時間とパラメータの効果時間の長い方を優先して設定する。
        /// </summary>
        /// <param name="skillId">設定する効果のスキルID。</param>
        /// <param name="timeMillis">設定する効果の持続時間。無限の場合は0。</param>
        public void setSkillEffect( int skillId, int timeMillis )
        {
            if ( hasSkillEffect( skillId ) )
            {
                int remainingTimeMills = getSkillEffectTimeSec( skillId ) * 1000;

                // 残り時間が有限で、パラメータの効果時間の方が長いか無限の場合は上書きする。
                if ( remainingTimeMills >= 0
                        && ( remainingTimeMills < timeMillis || timeMillis == 0 ) )
                {
                    killSkillEffectTimer( skillId );
                    addSkillEffect( skillId, timeMillis );
                }
            }
            else
            {
                addSkillEffect( skillId, timeMillis );
            }
        }

        /// <summary>
        /// キャラクターから、スキル効果を削除する。
        /// </summary>
        /// <param name="skillId">削除する効果のスキルID</param>
        public void removeSkillEffect( int skillId )
        {
            L1SkillTimer timer;
            _skillEffect.TryGetValue( skillId, out timer );
            if ( timer != null )
            {
                _skillEffect.Remove( skillId );
                timer.end();
            }
        }

        /// <summary>
        /// キャラクターから、スキル効果のタイマーを削除する。 スキル効果は削除されない。
        /// </summary>
        /// <param name="skillId">削除するタイマーのスキルＩＤ</param>
        public void killSkillEffectTimer( int skillId )
        {
            L1SkillTimer timer;
            _skillEffect.TryGetValue( skillId, out timer );
            if ( timer != null )
            {
                timer.kill();
            }
        }

        /// <summary>
        /// キャラクターから、全てのスキル効果タイマーを削除する。スキル効果は削除されない。
        /// </summary>
        public void clearSkillEffectTimer()
        {
            foreach ( L1SkillTimer timer in _skillEffect.Values )
            {
                if ( timer != null )
                {
                    timer.kill();
                }
            }
            _skillEffect.Clear();
        }

        /// <summary>
        /// キャラクターに、スキル効果が掛かっているかを返す。
        /// </summary>
        /// <param name="skillId">調べる効果のスキルID。</param>
        /// <returns>魔法効果があればtrue、なければfalse。</returns>
        public bool hasSkillEffect( int skillId )
        {
            return _skillEffect.ContainsKey( skillId );
        }

        /// <summary>
        /// キャラクターのスキル効果の持続時間を返す。
        /// </summary>
        /// <param name="skillId">調べる効果のスキルID</param>
        /// <returns>スキル効果の残り時間(秒)。スキルがかかっていないか効果時間が無限の場合、-1。</returns>
        public int getSkillEffectTimeSec( int skillId )
        {
            L1SkillTimer timer;
            _skillEffect.TryGetValue( skillId, out timer );
            if ( timer == null )
            {
                return -1;
            }
            return timer.getRemainingTime();
        }

        private bool _isSkillDelay = false;

        /// <summary>
        /// キャラクターへ、スキルディレイを追加する。
        /// </summary>
        /// <param name="flag"></param>
        public void setSkillDelay( bool flag )
        {
            _isSkillDelay = flag;
        }

        /// <summary>
        /// キャラクターの毒状態を返す。
        /// </summary>
        /// <returns>スキルディレイ中か。</returns>
        public bool isSkillDelay()
        {
            return _isSkillDelay;
        }

        /// <summary>
        /// キャラクターへ、アイテムディレイを追加する。
        /// </summary>
        /// <param name="delayId">アイテムディレイID。 通常のアイテムであれば0、インビジビリティ クローク、バルログ ブラッディ クロークであれば1。</param>
        /// <param name="timer">ディレイ時間を表す、L1ItemDelay.ItemDelayTimerオブジェクト。</param>
        public void addItemDelay( int delayId, L1ItemDelay timer )
        {
            _itemdelay.Add( delayId, timer );
        }

        /// <summary>
        /// キャラクターから、アイテムディレイを削除する。
        /// </summary>
        /// <param name="delayId">アイテムディレイID。 通常のアイテムであれば0、インビジビリティ クローク、バルログ ブラッディ クロークであれば1。</param>
        public void removeItemDelay( int delayId )
        {
            _itemdelay.Remove( delayId );
        }

        /// <summary>
        /// キャラクターに、アイテムディレイがあるかを返す。
        /// </summary>
        /// <param name="delayId">調べるアイテムディレイID。 通常のアイテムであれば0、インビジビリティ クローク、バルログ ブラッディ クロークであれば1。</param>
        /// <returns>アイテムディレイがあればtrue、なければfalse。</returns>
        public bool hasItemDelay( int delayId )
        {
            return _itemdelay.ContainsKey( delayId );
        }

        /// <summary>
        /// キャラクターのアイテムディレイ時間を表す、L1ItemDelay.ItemDelayTimerを返す。
        /// </summary>
        /// <param name="delayId">調べるアイテムディレイID。 通常のアイテムであれば0、インビジビリティ クローク、バルログ ブラッディ クロークであれば1。</param>
        /// <returns>アイテムディレイ時間を表す、L1ItemDelay.ItemDelayTimer。</returns>
        public L1ItemDelay getItemDelayTimer( int delayId )
        {
            return _itemdelay[delayId];
        }

        /// <summary>
        /// キャラクターへ、新たにペット、サモンモンスター、テイミングモンスター、あるいはクリエイトゾンビを追加する。
        /// </summary>
        /// <param name="npc">追加するNpcを表す、L1NpcInstanceオブジェクト。</param>
        public void addPet( L1NpcInstance npc )
        {
            _petlist[npc.getId()] = npc;
        }

        /// <summary>
        /// キャラクターから、ペット、サモンモンスター、テイミングモンスター、あるいはクリエイトゾンビを削除する。
        /// </summary>
        /// <param name="npc">削除するNpcを表す、L1NpcInstanceオブジェクト。</param>
        public void removePet( L1NpcInstance npc )
        {
            _petlist.Remove( npc.getId() );
        }

        /// <summary>
        /// キャラクターのペットリストを返す。
        /// </summary>
        /// <returns>キャラクターのペットリストを表す、HashMapオブジェクト。このオブジェクトのKeyはオブジェクトID、ValueはL1NpcInstance。</returns>
        public Dictionary<int, L1NpcInstance> getPetList()
        {
            return _petlist;
        }

        /// <summary>
        /// キャラクターへマジックドールを追加する。
        /// </summary>
        /// <param name="doll">追加するdollを表す、L1DollInstanceオブジェクト。</param>
        public void addDoll( L1DollInstance doll )
        {
            _dolllist[doll.getId()] = doll;
        }

        /// <summary>
        /// キャラクターからマジックドールを削除する。
        /// </summary>
        /// <param name="doll">削除するdollを表す、L1DollInstanceオブジェクト。</param>
        public void removeDoll( L1DollInstance doll )
        {
            _dolllist.Remove( doll.getId() );
        }

        /// <summary>
        /// キャラクターのマジックドールリストを返す。
        /// </summary>
        /// <returns>キャラクターの魔法人形リストを表す、HashMapオブジェクト。このオブジェクトのKeyはオブジェクトID、ValueはL1DollInstance。</returns>
        public Dictionary<int, L1DollInstance> getDollList()
        {
            return _dolllist;
        }

        /// <summary>
        /// キャラクターへ従者を追加する。
        /// </summary>
        /// <param name="follower">追加するfollowerを表す、L1FollowerInstanceオブジェクト。</param>
        public void addFollower( L1FollowerInstance follower )
        {
            _followerlist[follower.getId()] = follower;
        }

        /// <summary>
        /// キャラクターから従者を削除する。
        /// </summary>
        /// <param name="follower">削除するfollowerを表す、L1FollowerInstanceオブジェクト。</param>
        public void removeFollower( L1FollowerInstance follower )
        {
            _followerlist.Remove( follower.getId() );
        }

        /// <summary>
        /// キャラクターの従者リストを返す。
        /// </summary>
        /// <returns>キャラクターの従者リストを表す、HashMapオブジェクト。このオブジェクトのKeyはオブジェクトID、ValueはL1FollowerInstance。</returns>
        public Dictionary<int, L1FollowerInstance> getFollowerList()
        {
            return _followerlist;
        }

        /// <summary>
        /// キャラクターへ、毒を追加する。
        /// </summary>
        /// <param name="poison">毒を表す、L1Poisonオブジェクト。</param>
        public void setPoison( L1Poison poison )
        {
            _poison = poison;
        }

        /// <summary>
        /// キャラクターの毒を治療する。
        /// </summary>
        public void curePoison()
        {
            if ( _poison == null )
            {
                return;
            }
            _poison.cure();
        }

        /// <summary>
        /// キャラクターの毒状態を返す。
        /// </summary>
        /// <returns>キャラクターの毒を表す、L1Poisonオブジェクト。</returns>
        public L1Poison getPoison()
        {
            return _poison;
        }

        /// <summary>
        /// キャラクターへ毒のエフェクトを付加する
        /// </summary>
        /// <param name="effectId"></param>
        virtual public void setPoisonEffect( int effectId )
        {
            broadcastPacket( new S_Poison( getId(), effectId ) );
        }

        /// <summary>
        /// キャラクターが存在する座標が、どのゾーンに属しているかを返す。
        /// </summary>
        /// <returns>座標のゾーンを表す値。セーフティーゾーンであれば1、コンバットゾーンであれば-1、ノーマルゾーンであれば0。</returns>
        public int getZoneType()
        {
            if ( getMap().isSafetyZone( getLocation() ) )
            {
                return 1;
            }
            else if ( getMap().isCombatZone( getLocation() ) )
            {
                return -1;
            }
            else
            { // ノーマルゾーン
                return 0;
            }
        }

        private int _exp; // ● 経験値

        /// <summary>
        /// キャラクターが保持している経験値を返す。
        /// </summary>
        /// <returns>経験値。</returns>
        virtual public int getExp()
        {
            return _exp;
        }

        /// <summary>
        /// キャラクターが保持する経験値を設定する。
        /// </summary>
        /// <param name="exp">経験値</param>
        virtual public void setExp( int exp )
        {
            _exp = exp;
        }

        // ■■■■■■■■■■ L1PcInstanceへ移動するプロパティ ■■■■■■■■■■
        /// <summary>認識済みのオブジェクト(L1Object)</summary>
        private ArrayList _knownObjects = ArrayList.Synchronized( new ArrayList() );
        /// <summary>認識済みのプレイヤー(L1PcInstance)</summary>
        private ArrayList _knownPlayer = ArrayList.Synchronized( new ArrayList() );

        /// <summary>
        /// 指定されたオブジェクトを、キャラクターが認識しているかを返す。
        /// </summary>
        /// <param name="obj">調べるオブジェクト。</param>
        /// <returns>オブジェクトをキャラクターが認識していればtrue、していなければfalse。 自分自身に対してはfalseを返す。</returns>
        public bool knownsObject( L1Object obj )
        {
            return _knownObjects.Contains( obj );
        }

        /// <summary>
        /// キャラクターが認識している全てのオブジェクトを返す。
        /// </summary>
        /// <returns>キャラクターが認識しているオブジェクトを表すL1Objectが格納されたArrayList。</returns>
        //public ArrayList getKnownObjects()
        public object[] getKnownObjects()
        {
            //return _knownObjects;
            lock ( _knownObjects )
            {
                return _knownObjects.ToArray();
            }
        }

        /// <summary>
        /// キャラクターが認識している全てのプレイヤーを返す。
        /// </summary>
        /// <returns>キャラクターが認識しているオブジェクトを表すL1PcInstanceが格納されたArrayList。</returns>
        public ArrayList getKnownPlayers()
        {
            return _knownPlayer;
        }

        /// <summary>
        /// キャラクターに、新たに認識するオブジェクトを追加する。
        /// </summary>
        /// <param name="obj">新たに認識するオブジェクト。</param>
        public void addKnownObject( L1Object obj )
        {
            if ( !_knownObjects.Contains( obj ) )
            {
                _knownObjects.Add( obj );
                if ( obj is L1PcInstance )
                {
                    _knownPlayer.Add( (L1PcInstance)obj );
                }
            }
        }

        /// <summary>
        /// キャラクターから、認識しているオブジェクトを削除する。
        /// </summary>
        /// <param name="obj"> 削除するオブジェクト。</param>
        public void removeKnownObject( L1Object obj )
        {
            _knownObjects.Remove( obj );
            if ( obj is L1PcInstance )
            {
                _knownPlayer.Remove( obj as L1PcInstance );
            }
        }

        /// <summary>
        /// キャラクターから、全ての認識しているオブジェクトを削除する。
        /// </summary>
        public void removeAllKnownObjects()
        {
            _knownObjects.Clear();
            _knownPlayer.Clear();
        }

        // ■■■■■■■■■■ プロパティ ■■■■■■■■■■

        private String _name; // ● 名前

        public String getName()
        {
            return _name;
        }

        public void setName( String s )
        {
            _name = s;
        }

        private int _level; // ● レベル
        public int getLevel()
        {
            lock ( this )
            {
                return _level;
            }
        }

        public void setLevel( long level )
        {
            lock ( this )
            {
                _level = (int)level;
            }
        }

        private short _maxHp = 0; // ● ＭＡＸＨＰ（1～32767）
        private int _trueMaxHp = 0; // ● 本当のＭＡＸＨＰ

        public short getMaxHp()
        {
            return _maxHp;
        }

        public void setMaxHp( int hp )
        {
            _trueMaxHp = hp;
            _maxHp = (short)IntRange.ensure( _trueMaxHp, 1, 32767 );
            _currentHp = Math.Min( _currentHp, _maxHp );
        }

        public void addMaxHp( int i )
        {
            setMaxHp( _trueMaxHp + i );
        }

        private short _maxMp = 0; // ● ＭＡＸＭＰ（0～32767）
        private int _trueMaxMp = 0; // ● 本当のＭＡＸＭＰ

        public short getMaxMp()
        {
            return _maxMp;
        }

        public void setMaxMp( int mp )
        {
            _trueMaxMp = mp;
            _maxMp = (short)IntRange.ensure( _trueMaxMp, 0, 32767 );
            _currentMp = Math.Min( _currentMp, _maxMp );
        }

        public void addMaxMp( int i )
        {
            setMaxMp( _trueMaxMp + i );
        }

        private int _ac = 0; // ● ＡＣ（-128～127）
        private int _trueAc = 0; // ● 本当のＡＣ

        public int getAc()
        {
            return _ac;
        }

        public void setAc( int i )
        {
            _trueAc = i;
            _ac = IntRange.ensure( i, -128, 127 );
        }

        public void addAc( int i )
        {
            setAc( _trueAc + i );
        }

        private byte _str = 0; // ● ＳＴＲ（1～127）
        private short _trueStr = 0; // ● 本当のＳＴＲ

        public byte getStr()
        {
            return _str;
        }

        public void setStr( int i )
        {
            _trueStr = (short)i;
            _str = (byte)IntRange.ensure( i, 1, 127 );
        }

        public void addStr( int i )
        {
            setStr( _trueStr + i );
        }

        private byte _con = 0; // ● ＣＯＮ（1～127）
        private short _trueCon = 0; // ● 本当のＣＯＮ

        public byte getCon()
        {
            return _con;
        }

        public void setCon( int i )
        {
            _trueCon = (short)i;
            _con = (byte)IntRange.ensure( i, 1, 127 );
        }

        public void addCon( int i )
        {
            setCon( _trueCon + i );
        }

        private byte _dex = 0; // ● ＤＥＸ（1～127）
        private short _trueDex = 0; // ● 本当のＤＥＸ

        public byte getDex()
        {
            return _dex;
        }

        public void setDex( int i )
        {
            _trueDex = (short)i;
            _dex = (byte)IntRange.ensure( i, 1, 127 );
        }

        public void addDex( int i )
        {
            setDex( _trueDex + i );
        }

        private byte _cha = 0; // ● ＣＨＡ（1～127）
        private short _trueCha = 0; // ● 本当のＣＨＡ

        public byte getCha()
        {
            return _cha;
        }

        public void setCha( int i )
        {
            _trueCha = (short)i;
            _cha = (byte)IntRange.ensure( i, 1, 127 );
        }

        public void addCha( int i )
        {
            setCha( _trueCha + i );
        }

        private byte _int = 0; // ● ＩＮＴ（1～127）
        private short _trueInt = 0; // ● 本当のＩＮＴ

        public byte getInt()
        {
            return _int;
        }

        public void setInt( int i )
        {
            _trueInt = (short)i;
            _int = (byte)IntRange.ensure( i, 1, 127 );
        }

        public void addInt( int i )
        {
            setInt( _trueInt + i );
        }

        private byte _wis = 0; // ● ＷＩＳ（1～127）
        private short _trueWis = 0; // ● 本当のＷＩＳ

        public byte getWis()
        {
            return _wis;
        }

        public void setWis( int i )
        {
            _trueWis = (short)i;
            _wis = (byte)IntRange.ensure( i, 1, 127 );
        }

        public void addWis( int i )
        {
            setWis( _trueWis + i );
        }

        private int _wind = 0; // ● 風防御（-128～127）
        private int _trueWind = 0; // ● 本当の風防御

        public int getWind()
        {
            return _wind;
        } // 使用するとき

        public void addWind( int i )
        {
            _trueWind += i;
            if ( _trueWind >= 127 )
            {
                _wind = 127;
            }
            else if ( _trueWind <= -128 )
            {
                _wind = -128;
            }
            else
            {
                _wind = _trueWind;
            }
        }

        private int _water = 0; // ● 水防御（-128～127）
        private int _trueWater = 0; // ● 本当の水防御

        public int getWater()
        {
            return _water;
        } // 使用するとき

        public void addWater( int i )
        {
            _trueWater += i;
            if ( _trueWater >= 127 )
            {
                _water = 127;
            }
            else if ( _trueWater <= -128 )
            {
                _water = -128;
            }
            else
            {
                _water = _trueWater;
            }
        }

        private int _fire = 0; // ● 火防御（-128～127）
        private int _trueFire = 0; // ● 本当の火防御

        public int getFire()
        {
            return _fire;
        } // 使用するとき

        public void addFire( int i )
        {
            _trueFire += i;
            if ( _trueFire >= 127 )
            {
                _fire = 127;
            }
            else if ( _trueFire <= -128 )
            {
                _fire = -128;
            }
            else
            {
                _fire = _trueFire;
            }
        }

        private int _earth = 0; // ● 地防御（-128～127）
        private int _trueEarth = 0; // ● 本当の地防御

        public int getEarth()
        {
            return _earth;
        } // 使用するとき

        public void addEarth( int i )
        {
            _trueEarth += i;
            if ( _trueEarth >= 127 )
            {
                _earth = 127;
            }
            else if ( _trueEarth <= -128 )
            {
                _earth = -128;
            }
            else
            {
                _earth = _trueEarth;
            }
        }

        private int _addAttrKind; // エレメンタルフォールダウンで減少した属性の種類

        public int getAddAttrKind()
        {
            return _addAttrKind;
        }

        public void setAddAttrKind( int i )
        {
            _addAttrKind = i;
        }

        // スタン耐性
        private int _registStun = 0;
        private int _trueRegistStun = 0;

        public int getRegistStun()
        {
            return _registStun;
        } // 使用するとき

        public void addRegistStun( int i )
        {
            _trueRegistStun += i;
            if ( _trueRegistStun > 127 )
            {
                _registStun = 127;
            }
            else if ( _trueRegistStun < -128 )
            {
                _registStun = -128;
            }
            else
            {
                _registStun = _trueRegistStun;
            }
        }

        // 石化耐性
        private int _registStone = 0;
        private int _trueRegistStone = 0;

        public int getRegistStone()
        {
            return _registStone;
        } // 使用するとき

        public void addRegistStone( int i )
        {
            _trueRegistStone += i;
            if ( _trueRegistStone > 127 )
            {
                _registStone = 127;
            }
            else if ( _trueRegistStone < -128 )
            {
                _registStone = -128;
            }
            else
            {
                _registStone = _trueRegistStone;
            }
        }

        // 睡眠耐性
        private int _registSleep = 0;
        private int _trueRegistSleep = 0;

        public int getRegistSleep()
        {
            return _registSleep;
        } // 使用するとき

        public void addRegistSleep( int i )
        {
            _trueRegistSleep += i;
            if ( _trueRegistSleep > 127 )
            {
                _registSleep = 127;
            }
            else if ( _trueRegistSleep < -128 )
            {
                _registSleep = -128;
            }
            else
            {
                _registSleep = _trueRegistSleep;
            }
        }

        // 凍結耐性
        private int _registFreeze = 0;
        private int _trueRegistFreeze = 0;

        public int getRegistFreeze()
        {
            return _registFreeze;
        } // 使用するとき

        public void add_regist_freeze( int i )
        {
            _trueRegistFreeze += i;
            if ( _trueRegistFreeze > 127 )
            {
                _registFreeze = 127;
            }
            else if ( _trueRegistFreeze < -128 )
            {
                _registFreeze = -128;
            }
            else
            {
                _registFreeze = _trueRegistFreeze;
            }
        }

        // ホールド耐性
        private int _registSustain = 0;
        private int _trueRegistSustain = 0;

        public int getRegistSustain()
        {
            return _registSustain;
        } // 使用するとき

        public void addRegistSustain( int i )
        {
            _trueRegistSustain += i;
            if ( _trueRegistSustain > 127 )
            {
                _registSustain = 127;
            }
            else if ( _trueRegistSustain < -128 )
            {
                _registSustain = -128;
            }
            else
            {
                _registSustain = _trueRegistSustain;
            }
        }

        // 暗闇耐性
        private int _registBlind = 0;
        private int _trueRegistBlind = 0;

        public int getRegistBlind()
        {
            return _registBlind;
        } // 使用するとき

        public void addRegistBlind( int i )
        {
            _trueRegistBlind += i;
            if ( _trueRegistBlind > 127 )
            {
                _registBlind = 127;
            }
            else if ( _trueRegistBlind < -128 )
            {
                _registBlind = -128;
            }
            else
            {
                _registBlind = _trueRegistBlind;
            }
        }

        private int _dmgup = 0; // ● ダメージ補正（-128～127）
        private int _trueDmgup = 0; // ● 本当のダメージ補正

        public int getDmgup()
        {
            return _dmgup;
        } // 使用するとき

        public void addDmgup( int i )
        {
            _trueDmgup += i;
            if ( _trueDmgup >= 127 )
            {
                _dmgup = 127;
            }
            else if ( _trueDmgup <= -128 )
            {
                _dmgup = -128;
            }
            else
            {
                _dmgup = _trueDmgup;
            }
        }

        private int _bowDmgup = 0; // ● 弓ダメージ補正（-128～127）
        private int _trueBowDmgup = 0; // ● 本当の弓ダメージ補正

        public int getBowDmgup()
        {
            return _bowDmgup;
        } // 使用するとき

        public void addBowDmgup( int i )
        {
            _trueBowDmgup += i;
            if ( _trueBowDmgup >= 127 )
            {
                _bowDmgup = 127;
            }
            else if ( _trueBowDmgup <= -128 )
            {
                _bowDmgup = -128;
            }
            else
            {
                _bowDmgup = _trueBowDmgup;
            }
        }

        private int _hitup = 0; // ● 命中補正（-128～127）
        private int _trueHitup = 0; // ● 本当の命中補正

        public int getHitup()
        {
            return _hitup;
        } // 使用するとき

        public void addHitup( int i )
        {
            _trueHitup += i;
            if ( _trueHitup >= 127 )
            {
                _hitup = 127;
            }
            else if ( _trueHitup <= -128 )
            {
                _hitup = -128;
            }
            else
            {
                _hitup = _trueHitup;
            }
        }

        private int _bowHitup = 0; // ● 弓命中補正（-128～127）
        private int _trueBowHitup = 0; // ● 本当の弓命中補正

        public int getBowHitup()
        {
            return _bowHitup;
        } // 使用するとき

        public void addBowHitup( int i )
        {
            _trueBowHitup += i;
            if ( _trueBowHitup >= 127 )
            {
                _bowHitup = 127;
            }
            else if ( _trueBowHitup <= -128 )
            {
                _bowHitup = -128;
            }
            else
            {
                _bowHitup = _trueBowHitup;
            }
        }

        private int _mr = 0; // ● 魔法防御（0～）
        private int _trueMr = 0; // ● 本当の魔法防御

        public int getMr()
        {
            if ( hasSkillEffect( 153 ) == true )
            {
                return _mr / 4;
            }
            else
            {
                return _mr;
            }
        } // 使用するとき

        public int getTrueMr()
        {
            return _trueMr;
        } // セットするとき

        public void addMr( int i )
        {
            _trueMr += i;
            if ( _trueMr <= 0 )
            {
                _mr = 0;
            }
            else
            {
                _mr = _trueMr;
            }
        }

        private int _sp = 0; // ● 増加したＳＰ

        public int getSp()
        {
            return getTrueSp() + _sp;
        }

        virtual
        public int getTrueSp()
        {
            return getMagicLevel() + getMagicBonus();
        }

        public void addSp( int i )
        {
            _sp += i;
        }

        private bool _isDead; // ● 死亡状態

        public bool isDead()
        {
            return _isDead;
        }

        public void setDead( bool flag )
        {
            _isDead = flag;
        }

        private int _status; // ● 状態？

        public int getStatus()
        {
            return _status;
        }

        public void setStatus( int i )
        {
            _status = i;
        }

        private String _title; // ● タイトル

        public String getTitle()
        {
            return _title;
        }

        public void setTitle( String s )
        {
            _title = s;
        }

        private int _lawful; // ● アライメント

        public int getLawful()
        {
            return _lawful;
        }

        public void setLawful( int i )
        {
            _lawful = i;
        }

        public void addLawful( int i )
        {
            lock ( this )
            {
                _lawful += i;
                if ( _lawful > 32767 )
                {
                    _lawful = 32767;
                }
                else if ( _lawful < -32768 )
                {
                    _lawful = -32768;
                }
            }
        }

        private int _heading; // ● 向き 0.左上 1.上 2.右上 3.右 4.右下 5.下 6.左下 7.左

        public int getHeading()
        {
            return _heading;
        }

        public void setHeading( int i )
        {
            _heading = i;
        }

        private int _moveSpeed; // ● スピード 0.通常 1.ヘイスト 2.スロー

        public int getMoveSpeed()
        {
            return _moveSpeed;
        }

        public void setMoveSpeed( int i )
        {
            _moveSpeed = i;
        }

        private int _braveSpeed; // ● ブレイブ状態 0.通常 1.ブレイブ

        public int getBraveSpeed()
        {
            return _braveSpeed;
        }

        public void setBraveSpeed( int i )
        {
            _braveSpeed = i;
        }

        private int _tempCharGfx; // ● ベースグラフィックＩＤ

        public int getTempCharGfx()
        {
            return _tempCharGfx;
        }

        public void setTempCharGfx( int i )
        {
            _tempCharGfx = i;
        }

        private int _gfxid; // ● グラフィックＩＤ

        public int getGfxId()
        {
            return _gfxid;
        }

        public void setGfxId( int i )
        {
            _gfxid = i;
        }

        virtual
        public int getMagicLevel()
        {
            return getLevel() / 4;
        }

        public int getMagicBonus()
        {
            int i = getInt();
            if ( i <= 5 )
            {
                return -2;
            }
            else if ( i <= 8 )
            {
                return -1;
            }
            else if ( i <= 11 )
            {
                return 0;
            }
            else if ( i <= 14 )
            {
                return 1;
            }
            else if ( i <= 17 )
            {
                return 2;
            }
            else if ( i <= 24 )
            {
                return i - 15;
            }
            else if ( i <= 35 )
            {
                return 10;
            }
            else if ( i <= 42 )
            {
                return 11;
            }
            else if ( i <= 49 )
            {
                return 12;
            }
            else if ( i <= 50 )
            {
                return 13;
            }
            else
            {
                return i - 25;
            }
        }

        public bool isInvisble()
        {
            return ( hasSkillEffect( L1SkillId.INVISIBILITY ) || hasSkillEffect( L1SkillId.BLIND_HIDING ) );
        }

        virtual
        public void healHp( int pt )
        {
            setCurrentHp( getCurrentHp() + pt );
        }

        private int _karma;

        /*
         * キャラクターが保持しているカルマを返す。
         * 
         * @return カルマ。
         */
        public virtual int getKarma()
        {
            return _karma;
        }

        /// <summary>
        /// キャラクターが保持するカルマを設定する。
        /// </summary>
        /// <param name="karma">カルマ</param>
        public virtual void setKarma( int karma )
        {
            _karma = karma;
        }

        public void setMr( int i )
        {
            _trueMr = i;
            if ( _trueMr <= 0 )
            {
                _mr = 0;
            }
            else
            {
                _mr = _trueMr;
            }
        }

        public void turnOnOffLight()
        {
            int lightSize = 0;
            if ( this is L1NpcInstance )
            {
                L1NpcInstance npc = (L1NpcInstance)this;
                lightSize = npc.getLightSize(); // npc.sqlのライトサイズ
            }
            if ( hasSkillEffect( L1SkillId.LIGHT ) )
            {
                lightSize = 14;
            }

            L1PcInventory lpi = getInventory();
            if ( lpi != null )
            {
                foreach ( L1ItemInstance item in lpi.getItems() )
                {
                    if ( item.getItem().getType2() == 0 && item.getItem()
                            .getType() == 2 )
                    { // light系アイテム
                        int itemlightSize = item.getItem().getLightRange();
                        if ( itemlightSize != 0 && item.isNowLighting() )
                        {
                            if ( itemlightSize > lightSize )
                            {
                                lightSize = itemlightSize;
                            }
                        }
                    }
                }
            }

            if ( this is L1PcInstance )
            {
                L1PcInstance pc = (L1PcInstance)this;
                pc.sendPackets( new S_Light( pc.getId(), lightSize ) );
            }
            if ( !isInvisble() )
            {
                broadcastPacket( new S_Light( getId(), lightSize ) );
            }

            setOwnLightSize( lightSize ); // S_OwnCharPackのライト範囲
            setChaLightSize( lightSize ); // S_OtherCharPack, S_NPCPackなどのライト範囲
        }

        private int _chaLightSize; // ● ライトの範囲

        public int getChaLightSize()
        {
            if ( isInvisble() )
            {
                return 0;
            }
            return _chaLightSize;
        }

        public void setChaLightSize( int i )
        {
            _chaLightSize = i;
        }

        private int _ownLightSize; // ● ライトの範囲(S_OwnCharPack用)

        public int getOwnLightSize()
        {
            return _ownLightSize;
        }

        public void setOwnLightSize( int i )
        {
            _ownLightSize = i;
        }
    }
}
